﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.StyleCop;
using Microsoft.StyleCop.CSharp;
using Wintellect.PowerCollections;

namespace StyleCopContrib.CSharp.Rules
{
	[SourceAnalyzer(typeof(CsParser))]
	public class DocumentAnalyzer : SourceAnalyzer
	{
		private readonly List<CodeWalkerNodeVisitor<DocumentAnalyzer>> _GlobalNodeHandlers;
		private readonly MultiDictionary<CsTokenType, CodeWalkerNodeVisitor<DocumentAnalyzer>> _NodeHandlers;
		private readonly List<CodeWalkerElementVisitor<DocumentAnalyzer>> _GlobalElementHandlers;
		private readonly MultiDictionary<ElementType, CodeWalkerElementVisitor<DocumentAnalyzer>> _ElementHandlers;

		public DocumentAnalyzer()
		{
			_GlobalNodeHandlers = new List<CodeWalkerNodeVisitor<DocumentAnalyzer>>();
			_NodeHandlers = new MultiDictionary<CsTokenType, CodeWalkerNodeVisitor<DocumentAnalyzer>>(true);
			_GlobalElementHandlers = new List<CodeWalkerElementVisitor<DocumentAnalyzer>>();
			_ElementHandlers = new MultiDictionary<ElementType, CodeWalkerElementVisitor<DocumentAnalyzer>>(true);

			AddRules();
		}

		/// <summary>
		/// Add new rules here.
		/// </summary>
		private void AddRules()
		{
			_NodeHandlers.Add(CsTokenType.WhiteSpace, SpacingRules.CheckTabsMustBeUsed);
			_ElementHandlers.Add(ElementType.UsingDirective, OrderingRules.CheckUsingDirectivesMustBeginDocument);
			_ElementHandlers.Add(ElementType.Field, NamingRules.CheckPrivateFieldUnderscorePrefix);
			_ElementHandlers.Add(ElementType.Field, NamingRules.CheckPrivateFieldCasing);
		}

		public override void AnalyzeDocument(CodeDocument document)
		{
			var csDocument = document as CsDocument;
			if (csDocument != null)
			{
				try
				{
					WalkDocumentNodes(csDocument);
					csDocument.WalkDocument(VisitElement, this);
				}
				catch (Exception e)
				{
					Log(StyleCopLogLevel.High, e.ToString());
					AddViolation(null, 1, RuleNames.ExceptionOccurred, e.GetType(), e.ToString());
				}
			}
		}

		public T GetProperty<T>(CsDocument document, string propertyName)
		{
			if (document != null)
			{
				var property = GetSetting(document.Settings, propertyName) as PropertyValue<T>;
				if (property != null)
				{
					return property.Value;
				}
				var descriptor = PropertyDescriptors
					.GetPropertyDescriptor(propertyName) as PropertyDescriptor<T>;
				if (descriptor != null)
				{
					return descriptor.DefaultValue;
				}
			}
			return default(T);
		}

		public TEnum GetEnumProperty<TEnum>(CsDocument document, string propertyName)
			where TEnum : struct
		{
			var text = GetProperty<string>(document, propertyName);
			if (string.IsNullOrEmpty(text))
			{
				return (TEnum)System.Enum.ToObject(typeof(TEnum), 0);
			}
			var converter = new EnumConverter(typeof(TEnum));
			return (TEnum)converter.ConvertFromString(text);
		}

		private void WalkDocumentNodes(CsDocument document)
		{
			for (var node = document.Tokens.First; node != null; node = node.Next)
			{
				foreach (var handler in _GlobalNodeHandlers)
				{
					handler(document.RootElement, node, this);
				}
				foreach (var handler in _NodeHandlers[node.Value.CsTokenType])
				{
					handler(document.RootElement, node, this);
				}
			}
		}

		private bool VisitElement(CsElement element,
			CsElement parentElement,
			DocumentAnalyzer context)
		{
			if (element != null &&
				!element.Generated)
			{
				foreach (var handler in _GlobalElementHandlers)
				{
					handler(element, element.ParentElement, this);
				}
				foreach (var handler in _ElementHandlers[element.ElementType])
				{
					handler(element, element.ParentElement, this);
				}
			}
			return true;
		}
	}
}
